/*
 * Copyright (c) 2007, 2015, Oracle and/or its affiliates. All rights reserved.
 * ORACLE PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
 */
/*
 * The Apache Software License, Version 1.1
 *
 *
 * Copyright (c) 2001-2003 The Apache Software Foundation.  All rights
 * reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 *
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in
 *    the documentation and/or other materials provided with the
 *    distribution.
 *
 * 3. The end-user documentation included with the redistribution,
 *    if any, must include the following acknowledgment:
 *       "This product includes software developed by the
 *        Apache Software Foundation (http://www.apache.org/)."
 *    Alternately, this acknowledgment may appear in the software itself,
 *    if and wherever such third-party acknowledgments normally appear.
 *
 * 4. The names "Xerces" and "Apache Software Foundation" must
 *    not be used to endorse or promote products derived from this
 *    software without prior written permission. For written
 *    permission, please contact apache@apache.org.
 *
 * 5. Products derived from this software may not be called "Apache",
 *    nor may "Apache" appear in their name, without prior written
 *    permission of the Apache Software Foundation.
 *
 * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED.  IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
 * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
 * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
 * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 * ====================================================================
 *
 * This software consists of voluntary contributions made by many
 * individuals on behalf of the Apache Software Foundation and was
 * originally based on software copyright (c) 1999, International
 * Business Machines, Inc., http://www.apache.org.  For more
 * information on the Apache Software Foundation, please see
 * <http://www.apache.org/>.
 */
package com.sun.org.apache.xerces.internal.xinclude;


import java.util.Enumeration;
import java.util.StringTokenizer;
import java.util.Stack;

import com.sun.org.apache.xerces.internal.impl.Constants;
import com.sun.org.apache.xerces.internal.impl.XMLErrorReporter;
import com.sun.org.apache.xerces.internal.impl.dtd.DTDGrammar;
import com.sun.org.apache.xerces.internal.util.ParserConfigurationSettings;
import com.sun.org.apache.xerces.internal.xni.Augmentations;
import com.sun.org.apache.xerces.internal.xni.NamespaceContext;
import com.sun.org.apache.xerces.internal.xni.QName;
import com.sun.org.apache.xerces.internal.xni.XMLAttributes;
import com.sun.org.apache.xerces.internal.xni.XMLDocumentHandler;
import com.sun.org.apache.xerces.internal.xni.XMLLocator;
import com.sun.org.apache.xerces.internal.xni.XMLResourceIdentifier;
import com.sun.org.apache.xerces.internal.xni.XMLString;
import com.sun.org.apache.xerces.internal.xni.XNIException;
import com.sun.org.apache.xerces.internal.xni.grammars.XMLGrammarDescription;
import com.sun.org.apache.xerces.internal.xni.grammars.XMLGrammarPool;
import com.sun.org.apache.xerces.internal.xni.parser.XMLComponentManager;
import com.sun.org.apache.xerces.internal.xni.parser.XMLConfigurationException;
import com.sun.org.apache.xerces.internal.xni.parser.XMLDocumentSource;
import com.sun.org.apache.xerces.internal.xni.parser.XMLEntityResolver;
import com.sun.org.apache.xerces.internal.impl.dtd.XMLDTDValidator;

/**
 * @author Arun Yadav, Sun Microsystem
 */
public class XPointerElementHandler implements XPointerSchema {

  // recognized features and properties

  /**
   * Property identifier: error handler.
   */
  protected static final String ERROR_REPORTER =
      Constants.XERCES_PROPERTY_PREFIX + Constants.ERROR_REPORTER_PROPERTY;

  /**
   * Property identifier: grammar pool .
   */
  protected static final String GRAMMAR_POOL =
      Constants.XERCES_PROPERTY_PREFIX + Constants.XMLGRAMMAR_POOL_PROPERTY;

  /**
   * Property identifier: grammar pool .
   */
  protected static final String ENTITY_RESOLVER =
      Constants.XERCES_PROPERTY_PREFIX + Constants.ENTITY_RESOLVER_PROPERTY;

  protected static final String XPOINTER_SCHEMA =
      Constants.XERCES_PROPERTY_PREFIX + Constants.XPOINTER_SCHEMA_PROPERTY;

  /**
   * Recognized features.
   */
  private static final String[] RECOGNIZED_FEATURES = {
  };

  /**
   * Feature defaults.
   */
  private static final Boolean[] FEATURE_DEFAULTS = {
  };

  /**
   * Recognized properties.
   */

  private static final String[] RECOGNIZED_PROPERTIES =
      {ERROR_REPORTER, GRAMMAR_POOL, ENTITY_RESOLVER, XPOINTER_SCHEMA};

  /**
   * Property defaults.
   */
  private static final Object[] PROPERTY_DEFAULTS = {null, null, null, null};

  // Data

  protected XMLDocumentHandler fDocumentHandler;
  protected XMLDocumentSource fDocumentSource;

  protected XIncludeHandler fParentXIncludeHandler;

  protected XMLLocator fDocLocation;
  protected XIncludeNamespaceSupport fNamespaceContext;
  protected XMLErrorReporter fErrorReporter;
  protected XMLGrammarPool fGrammarPool;
  protected XMLGrammarDescription fGrammarDesc;
  protected DTDGrammar fDTDGrammar;
  protected XMLEntityResolver fEntityResolver;
  protected ParserConfigurationSettings fSettings;
  //protected String fPointerSchema;
  protected StringBuffer fPointer;
  private int elemCount = 0;


  // The current element depth.
  // This is used to access the appropriate level of the following stacks.
  private int fDepth;

  // The depth of the first element to actually be part of the result infoset.
  // This will normally be 1, but it could be larger when the top-level item
  // is an include, and processing goes to the fallback.
  private int fRootDepth;

  // this value must be at least 1
  private static final int INITIAL_SIZE = 8;


  // Used to ensure that fallbacks are always children of include elements,
  // and that include elements are never children of other include elements.
  // An index contains true if the ancestor of the current element which resides
  // at that depth was an include element.
  private boolean[] fSawInclude = new boolean[INITIAL_SIZE];


  // Ensures that only one fallback element can be at a single depth.
  // An index contains true if we have seen any fallback elements at that depth,
  // and it is only reset to false when the end tag of the parent is encountered.
  private boolean[] fSawFallback = new boolean[INITIAL_SIZE];


  // The state of the processor at each given depth.
  private int[] fState = new int[INITIAL_SIZE];

  QName foundElement = null;
  boolean skip = false;
  // Constructors

  public XPointerElementHandler() {

    fDepth = 0;
    fRootDepth = 0;
    fSawFallback[fDepth] = false;
    fSawInclude[fDepth] = false;
    fSchemaName = "element";


  }

  // START OF IMPLEMENTATION OF XMLComponent methods //////

  public void reset() {
    elemCount = 0;
    fPointerToken = null;
    fCurrentTokenint = 0;
    fCurrentTokenString = null;
    fCurrentTokenType = 0;
    fElementCount = 0;
    fCurrentToken = 0;
    includeElement = false;
    foundElement = null;
    skip = false;
    fSubResourceIdentified = false;
  }

  public void reset(XMLComponentManager componentManager)
      throws XNIException {
    fNamespaceContext = null;
    elemCount = 0;
    fDepth = 0;
    fRootDepth = 0;
    fPointerToken = null;
    fCurrentTokenint = 0;
    fCurrentTokenString = null;
    fCurrentTokenType = 0;
    foundElement = null;
    includeElement = false;
    skip = false;
    fSubResourceIdentified = false;

    try {
      setErrorReporter(
          (XMLErrorReporter) componentManager.getProperty(ERROR_REPORTER));
    } catch (XMLConfigurationException e) {
      fErrorReporter = null;
    }
    try {
      fGrammarPool =
          (XMLGrammarPool) componentManager.getProperty(GRAMMAR_POOL);
    } catch (XMLConfigurationException e) {
      fGrammarPool = null;
    }
    try {
      fEntityResolver =
          (XMLEntityResolver) componentManager.getProperty(
              ENTITY_RESOLVER);
    } catch (XMLConfigurationException e) {
      fEntityResolver = null;
    }

    fSettings = new ParserConfigurationSettings();

    Enumeration xercesFeatures = Constants.getXercesFeatures();
    while (xercesFeatures.hasMoreElements()) {
      String featureId = (String) xercesFeatures.nextElement();
      fSettings.addRecognizedFeatures(new String[]{featureId});
      try {
        fSettings.setFeature(
            featureId,
            componentManager.getFeature(featureId));
      } catch (XMLConfigurationException e) {
        // componentManager doesn't support this feature,
        // so we won't worry about it
      }
    }
/*              try{
          dtdValidator =   (XMLDTDValidator)componentManager.getProperty( Constants.XERCES_PROPERTY_PREFIX + Constants.DTD_VALIDATOR_PROPERTY);
                }Catch(Exception ex){
                        ex.printStackTrace();
                }*/

  } // reset(XMLComponentManager)

  /**
   * Returns a list of feature identifiers that are recognized by
   * this component. This method may return null if no features
   * are recognized by this component.
   */
  public String[] getRecognizedFeatures() {
    return RECOGNIZED_FEATURES;
  } // getRecognizedFeatures():String[]

  /**
   * Sets the state of a feature. This method is called by the component
   * manager any time after reset when a feature changes state.
   * <p>
   * <strong>Note:</strong> Components should silently ignore features
   * that do not affect the operation of the component.
   *
   * @param featureId The feature identifier.
   * @param state The state of the feature.
   * @throws SAXNotRecognizedException The component should not throw this exception.
   * @throws SAXNotSupportedException The component should not throw this exception.
   */
  public void setFeature(String featureId, boolean state)
      throws XMLConfigurationException {
    if (fSettings != null) {
      fSettings.setFeature(featureId, state);
    }

  } // setFeature(String,boolean)

  /**
   * Returns a list of property identifiers that are recognized by
   * this component. This method may return null if no properties
   * are recognized by this component.
   */
  public String[] getRecognizedProperties() {
    return RECOGNIZED_PROPERTIES;
  } // getRecognizedProperties():String[]

  /**
   * Sets the value of a property. This method is called by the component
   * manager any time after reset when a property changes value.
   * <p>
   * <strong>Note:</strong> Components should silently ignore properties
   * that do not affect the operation of the component.
   *
   * @param propertyId The property identifier.
   * @param value The value of the property.
   * @throws SAXNotRecognizedException The component should not throw this exception.
   * @throws SAXNotSupportedException The component should not throw this exception.
   */
  public void setProperty(String propertyId, Object value)
      throws XMLConfigurationException {
    if (propertyId.equals(ERROR_REPORTER)) {
      setErrorReporter((XMLErrorReporter) value);
    }
    if (propertyId.equals(GRAMMAR_POOL)) {
      fGrammarPool = (XMLGrammarPool) value;
    }
    if (propertyId.equals(ENTITY_RESOLVER)) {
      fEntityResolver = (XMLEntityResolver) value;
    }

  } // setProperty(String,Object)

  /**
   * Returns the default state for a feature, or null if this
   * component does not want to report a default value for this
   * feature.
   *
   * @param featureId The feature identifier.
   * @since Xerces 2.2.0
   */
  public Boolean getFeatureDefault(String featureId) {
    for (int i = 0; i < RECOGNIZED_FEATURES.length; i++) {
      if (RECOGNIZED_FEATURES[i].equals(featureId)) {
        return FEATURE_DEFAULTS[i];
      }
    }
    return null;
  } // getFeatureDefault(String):Boolean

  /**
   * Returns the default state for a property, or null if this
   * component does not want to report a default value for this
   * property.
   *
   * @param propertyId The property identifier.
   * @since Xerces 2.2.0
   */
  public Object getPropertyDefault(String propertyId) {
    for (int i = 0; i < RECOGNIZED_PROPERTIES.length; i++) {
      if (RECOGNIZED_PROPERTIES[i].equals(propertyId)) {
        return PROPERTY_DEFAULTS[i];
      }
    }
    return null;
  } // getPropertyDefault(String):Object

  private void setErrorReporter(XMLErrorReporter reporter) {
    fErrorReporter = reporter;
    if (fErrorReporter != null) {
      fErrorReporter.putMessageFormatter(
          XIncludeMessageFormatter.XINCLUDE_DOMAIN,
          new XIncludeMessageFormatter());
    }
  }
  ///////// END OF IMPLEMENTATION  OF XMLComponents methods. //////////

  //////// START OF  IMPLEMENTATION OF XMLDOCUMENTSOURCE INTERFACE /////////

  public void setDocumentHandler(XMLDocumentHandler handler) {
    fDocumentHandler = handler;
  }

  public XMLDocumentHandler getDocumentHandler() {
    return fDocumentHandler;
  }

  ///////   END OF IMPLENTATION OF XMLDOCUMENTSOURCE INTERFACE ///////////

  /////////////// Implementation of XPointerSchema Methods //////////////////////

  String fSchemaName;
  String fSchemaPointer;
  boolean fSubResourceIdentified;

  /**
   * set the Schema Name  eg element , xpointer
   */
  public void setXPointerSchemaName(String schemaName) {
    fSchemaName = schemaName;
  }

  /**
   * Return  Schema Name  eg element , xpointer
   */
  public String getXpointerSchemaName() {
    return fSchemaName;
  }

  /**
   * Parent Contenhandler for the this contenthandler.
   * // not sure about the parameter type. It can be Contenthandler instead of Object type.
   */
  public void setParent(Object parent) {
    fParentXIncludeHandler = (XIncludeHandler) parent;
  }


  /**
   * return the Parent Contenthandler
   */
  public Object getParent() {
    return fParentXIncludeHandler;
  }

  /**
   * Content of the XPointer Schema. Xpath to be resolved.
   */
  public void setXPointerSchemaPointer(String content) {
    fSchemaPointer = content;
  }

  /**
   * Return the XPointer Schema.
   */
  public String getXPointerSchemaPointer() {
    return fSchemaPointer;
  }

  public boolean isSubResourceIndentified() {
    return fSubResourceIdentified;
  }

  ///////////End Implementation of XPointerSchema Methods //////////////////////

  //////////// Tokens Playground ///////////////////

  Stack fPointerToken = new Stack();
  int fCurrentTokenint = 0;
  String fCurrentTokenString = null;
  int fCurrentTokenType = 0;// 0 Notype; 1 for integer; 2 for string.

  public void getTokens() {
    fSchemaPointer = fSchemaPointer
        .substring(fSchemaPointer.indexOf("(") + 1, fSchemaPointer.length());
    StringTokenizer st = new StringTokenizer(fSchemaPointer, "/");
    String tempToken;
    Integer integerToken = null;
    Stack tempPointerToken = new Stack();
    if (fPointerToken == null) {
      fPointerToken = new Stack();
    }
    while (st.hasMoreTokens()) {
      tempToken = st.nextToken();
      try {
        integerToken = Integer.valueOf(tempToken);
        tempPointerToken.push(integerToken);
      } catch (NumberFormatException e) {
        tempPointerToken.push(tempToken);
      }
    }
    while (!tempPointerToken.empty()) {
      fPointerToken.push(tempPointerToken.pop());
    }
  }//getTokens


  public boolean hasMoreToken() {
    if (fPointerToken.isEmpty()) {
      return false;
    } else {
      return true;
    }
  }

  public boolean getNextToken() {
    Object currentToken;
    if (!fPointerToken.isEmpty()) {
      currentToken = fPointerToken.pop();
      if (currentToken instanceof Integer) {
        fCurrentTokenint = ((Integer) currentToken).intValue();
        fCurrentTokenType = 1;
      } else {
        fCurrentTokenString = ((String) currentToken).toString();
        fCurrentTokenType = 2;
      }
      return true;
    } else {
      return false;
    }
  }

  private boolean isIdAttribute(XMLAttributes attributes, Augmentations augs, int index) {
    Object o = augs.getItem(Constants.ID_ATTRIBUTE);
    if (o instanceof Boolean) {
      return ((Boolean) o).booleanValue();
    }
    return "ID".equals(attributes.getType(index));
  }

  public boolean checkStringToken(QName element, XMLAttributes attributes) {
    QName cacheQName = null;
    String id = null;
    String rawname = null;
    QName attrName = new QName();
    String attrType = null;
    String attrValue = null;
    int attrCount = attributes.getLength();
    for (int i = 0; i < attrCount; i++) {
      Augmentations aaugs = attributes.getAugmentations(i);
      attributes.getName(i, attrName);
      attrType = attributes.getType(i);
      attrValue = attributes.getValue(i);
      if (attrType != null && attrValue != null && isIdAttribute(attributes, aaugs, i) && attrValue
          .equals(fCurrentTokenString)) {
        if (hasMoreToken()) {
          fCurrentTokenType = 0;
          fCurrentTokenString = null;
          return true;
        } else {
          foundElement = element;
          includeElement = true;
          fCurrentTokenType = 0;
          fCurrentTokenString = null;
          fSubResourceIdentified = true;
          return true;
        }
      }
    }
    return false;
  }

  public boolean checkIntegerToken(QName element) {
    if (!skip) {
      fElementCount++;
      if (fCurrentTokenint == fElementCount) {
        if (hasMoreToken()) {
          fElementCount = 0;
          fCurrentTokenType = 0;
          return true;
        } else {
          foundElement = element;
          includeElement = true;
          fCurrentTokenType = 0;
          fElementCount = 0;
          fSubResourceIdentified = true;
          return true;
        }
      } else {
        addQName(element);
        skip = true;
        return false;
      }
    }
    return false;
  }

  public void addQName(QName element) {
    QName cacheQName = new QName(element);
    ftempCurrentElement.push(cacheQName);
  }

  ///////////  END TOKEN PLAYGROUND ///////////////

  /////   START OF IMPLEMTATION OF XMLDocumentHandler methods //////////


  public void startDocument(XMLLocator locator, String encoding,
      NamespaceContext namespaceContext, Augmentations augs)
      throws XNIException {

    getTokens();
  }

  public void doctypeDecl(String rootElement, String publicId, String systemId,
      Augmentations augs) throws XNIException {
  }

  public void xmlDecl(String version, String encoding, String standalone,
      Augmentations augs) throws XNIException {
  }


  public void comment(XMLString text, Augmentations augs)
      throws XNIException {
    if (fDocumentHandler != null && includeElement) {
      fDocumentHandler.comment(text, augs);
    }
  }

  public void processingInstruction(String target, XMLString data,
      Augmentations augs) throws XNIException {
    if (fDocumentHandler != null && includeElement) {
      fDocumentHandler.processingInstruction(target, data, augs);

    }
  }

  Stack ftempCurrentElement = new Stack();
  int fElementCount = 0;
  int fCurrentToken;
  boolean includeElement;


  public void startElement(QName element, XMLAttributes attributes,
      Augmentations augs) throws XNIException {

    boolean requiredToken = false;
    if (fCurrentTokenType == 0) {
      getNextToken();
    }
    if (fCurrentTokenType == 1) {
      requiredToken = checkIntegerToken(element);
    } else if (fCurrentTokenType == 2) {
      requiredToken = checkStringToken(element, attributes);
    }
    if (requiredToken && hasMoreToken()) {
      getNextToken();
    }
    if (fDocumentHandler != null && includeElement) {
      elemCount++;
      fDocumentHandler.startElement(element, attributes, augs);
    }

  }


  public void endElement(QName element, Augmentations augs)
      throws XNIException {
    if (includeElement && foundElement != null) {
      if (elemCount > 0) {
        elemCount--;
      }
      fDocumentHandler.endElement(element, augs);
      if (elemCount == 0) {
        includeElement = false;
      }

    } else if (!ftempCurrentElement.empty()) {
      QName name = (QName) ftempCurrentElement.peek();
      if (name.equals(element)) {
        ftempCurrentElement.pop();
        skip = false;
      }
    }
  }

  public void emptyElement(QName element, XMLAttributes attributes,
      Augmentations augs) throws XNIException {
    if (fDocumentHandler != null && includeElement) {
      fDocumentHandler.emptyElement(element, attributes, augs);
    }
  }

  public void startGeneralEntity(String name, XMLResourceIdentifier resId,
      String encoding,
      Augmentations augs)
      throws XNIException {
    if (fDocumentHandler != null && includeElement) {
      fDocumentHandler.startGeneralEntity(name, resId, encoding, augs);
    }
  }

  public void textDecl(String version, String encoding, Augmentations augs)
      throws XNIException {
    if (fDocumentHandler != null && includeElement) {
      fDocumentHandler.textDecl(version, encoding, augs);
    }
  }

  public void endGeneralEntity(String name, Augmentations augs)
      throws XNIException {
    if (fDocumentHandler != null) {
      fDocumentHandler.endGeneralEntity(name, augs);
    }
  }

  public void characters(XMLString text, Augmentations augs)
      throws XNIException {
    if (fDocumentHandler != null && includeElement) {
      fDocumentHandler.characters(text, augs);
    }
  }

  public void ignorableWhitespace(XMLString text, Augmentations augs)
      throws XNIException {
    if (fDocumentHandler != null && includeElement) {
      fDocumentHandler.ignorableWhitespace(text, augs);
    }
  }

  public void startCDATA(Augmentations augs) throws XNIException {
    if (fDocumentHandler != null && includeElement) {
      fDocumentHandler.startCDATA(augs);
    }
  }

  public void endCDATA(Augmentations augs) throws XNIException {
    if (fDocumentHandler != null && includeElement) {
      fDocumentHandler.endCDATA(augs);
    }
  }

  public void endDocument(Augmentations augs) throws XNIException {
  }

  public void setDocumentSource(XMLDocumentSource source) {
    fDocumentSource = source;
  }

  public XMLDocumentSource getDocumentSource() {
    return fDocumentSource;
  }


  protected void reportFatalError(String key) {
    this.reportFatalError(key, null);
  }

  protected void reportFatalError(String key, Object[] args) {
    if (fErrorReporter != null) {
      fErrorReporter.reportError(
          fDocLocation,
          XIncludeMessageFormatter.XINCLUDE_DOMAIN,
          key,
          args,
          XMLErrorReporter.SEVERITY_FATAL_ERROR);
    }
    // we won't worry about when error reporter is null, since there should always be
    // at least the default error reporter
  }


  // used to know whether to pass declarations to the document handler
  protected boolean isRootDocument() {
    return this.fParentXIncludeHandler == null;
  }


} // class XPointerElementhandler
